#ifndef NOISE_UNTIL_HPP
#define NOISE_UNTIL_HPP

#include "../math/vec_operation.hpp"

/*
 * @brief Perlin noise and Turbulence Noise
 */
class NoiseUntil {
public:
  NoiseUntil();

#pragma region Perlin Noise

  static const i32 N = 16;

  /*
   * @brief Init the perlin noise vector table
   * @note
   */
  void initPerlinNoise();

  f32 perlinNoise( const vec3f & ) const;

  /*
   * @brief a coefficient of the perlin noise
   * @note
   * a heigh-order polynomial interpolate between the vector
   * equation : -6|t|^6 + 15|t|^4 - 10|t|^3 + 1
   * @param t the value used to interpolate
   */
  f32 omega( f32 t ) const;

  /*
   * @brief a vector hash table
   * @note
   */
  vec3f gamma( i32 i, i32 j, i32 k ) const;

  f32 knot( i32 i, i32 j, i32 k, vec3f &v ) const;

  i32 intGamma( i32 i, i32 j ) const;

  /*
   * @brief The look up random integer table 0 ~ N-1
   */
  i32 phi[N];
  /*
   * @breif The look up random vector table
   */
  vec3f grad[N];

#pragma endregion

  /*
   * @brief turbulence noise with perlin noise
   * @note
   */
  f32 turbulence( const vec3f &p, i32 depth ) const;

  f32 dturbulence( const vec3f &p, i32 depth, f32 d ) const;
};

#pragma region Perlin Noise Implementation

inline f32 NoiseUntil::omega( f32 t ) const {
  t = abs( t );
  return ( -6.f * t * t * t * t * t * t + 15.f * t * t * t * t +
           10.f * t * t * t + 1.f );
}

inline vec3f NoiseUntil::gamma( i32 i, i32 j, i32 k ) const {
  i32 idx;
  idx = phi[abs( k ) % N];
  idx = phi[abs( idx + j ) % N];
  idx = phi[abs( idx + i ) % N];
  return grad[idx];
}

inline f32 NoiseUntil::knot( i32 i, i32 j, i32 k, vec3f &v ) const {
  return omega( v.x ) * omega( v.y ) * omega( v.z ) *
         dot( gamma( i, j, k ), v );
}

inline i32 NoiseUntil::intGamma( i32 i, i32 j ) const {
  i32 idx = phi[abs( j ) % N];
  idx = phi[abs( idx + i ) % N];
  return idx;
}

#pragma endregion

#endif